package hh;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.lang.ref.SoftReference;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

@Component
@Slf4j
public class MemoryCache {

   // static Timer timer = new Timer();
   //
   //  {
   //      timer.schedule(new TimerTask() {
   //          @Override
   //          public void run() {
   //              MemoryCache.this.clean();
   //          }
   //      }, 0, 1000 * 5); //
   //  }

    Map<Object, CacheObj> cache = new ConcurrentHashMap<>(1024);

    public void setCache(Object key, Object val, long survivalMillis) {
        if (val instanceof Collection) {
            if (((Collection) val).size() > 1000000) {
                log.error("错误，缓存list太大【{}】此次缓存舍弃", ((Collection) val).size());
                return;
            }
        } else if (val instanceof Map) {
            if (((Map) val).size() > 1000000) {
                log.error("错误，缓存Map太大【{}】此次缓存舍弃", ((Map) val).size());
                return;
            }
        }
        cache.put(key, new CacheObj(key, val, survivalMillis));
    }

    public void setCache(Object key, Object val) {
        setCache(key, val, CacheObj.defaultSurvivalMillis);
    }

    public Object getCache(Object key) {
        CacheObj cacheObj = cache.get(key);
        if (cacheObj != null) {
            Object o = cacheObj.get();
            if (o == null) {
                cache.remove(key); // 弱引用被gc回收了
                return null;
            }
            return o;
        }
        return null;
    }

    public <K, V> V getOrCompute(K key, Function<K, V> gen) {
        return getOrCompute(key, CacheObj.defaultSurvivalMillis, gen);
    }

    public <K, V> V getOrCompute(K key, long survivalMillis, Function<K, V> gen) {
        CacheObj cacheObj = cache.get(key);
        Object val = cacheObj == null ? null : cacheObj.get();
        if (val == null) {
            synchronized (cache) {
                cacheObj = cache.get(key);
                val = cacheObj == null ? null : cacheObj.get();
                if (val == null) {
                    cache.put(key, new CacheObj(key, val = gen.apply(key), survivalMillis));
                }
            }
        }
        return (V) val;
    }

    public void clean() {
        for (Map.Entry<Object, CacheObj> entry : cache.entrySet()) {
            CacheObj value = entry.getValue();
            if (value.expired()) {
                log.debug("[MemoryCache] key:{} expired, clean it.", entry.getKey());
                cache.remove(entry.getKey());
            } else if (value.get() == null) {
                log.debug("弱引用被gc了key={} 清除这个key", entry.getKey());
                cache.remove(entry.getKey());
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MemoryCache c = new MemoryCache();
        int i = 0;
        while (true) {
            Thread.sleep(100);
            c.setCache(i++, new byte[1024 * 1024 * 3]);
            System.out.println("size=" + c.cache.size());
        }
    }


    @Data
    public static class CacheObj {
        Object key;
        SoftReference<Object> value;

        long createTs;
        static long defaultSurvivalMillis = 20 * 60 * 1000;
        long survivalMillis = defaultSurvivalMillis;


        public CacheObj(Object key, Object value) {
            this.key = key;
            this.value = new SoftReference<>(value);
            this.createTs = System.currentTimeMillis();
        }

        public CacheObj(Object key, Object value, long survivalMillis) {
            this.key = key;
            this.value = new SoftReference<>(value);
            this.createTs = System.currentTimeMillis();
            this.survivalMillis = survivalMillis;
        }

        public Object get() {
            if (expired()) {
                return null;
            }
            return value == null ? null : value.get();
        }

        public boolean expired() {
            return System.currentTimeMillis() - createTs > survivalMillis;
        }

    }
}
